Port to TypeScript with strict compiler and type-aware ESLint - #14
Conversation
server.test.mjs was a single 2000-line file covering seven modules. It is now four files that mirror the sources, plus test-helpers.ts for the fixtures, loadServerAs() and the mock factories. vitest scopes module mocks to the declaring file, so each test file repeats the node:fs and node:fs/promises mocks; test-helpers.ts then picks up the mocked copies through its own imports. 152 tests and 100% coverage unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The TypeScript port turned `options.timeout || 30000` into `?? 30000`, so a
caller passing { timeout: 0 } would get an immediate SIGTERM instead of the
default. Not reachable through the MCP tools — the dispatcher always sends
an explicit timeout — but SSHClient is exported, and a refactor should not
change its behaviour.
Found by an external review (codex). Adds a regression test with fake
timers so the falsy-fallback semantics stay pinned.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged as What landedThe self-contained
The suite is split along the same boundaries — four files plus What strictness actually caughtWorth recording honestly, because it is the answer to "was the port worth it": The linter nearly introduced a bug.
An external review (codex) found a real regression I had missed. The port turned Delivery chainThis is where a build step is genuinely risky — 1.3.6 and 1.3.8 were both entry-point/packaging regressions — so it was measured rather than assumed. Verified end to end: Both workflows now run typecheck, lint, the suite and a smoke test of the compiled server — the test matrix across Linux and Windows on Node 20/22/24, and the publish workflow before anything reaches npm. Cleanups that fell out
Not in scope, flagged for later: the SDK marks the low-level |
Splits the single 900-line
server.mjsinto seven typed modules undersrc/, compiled todist/bytsc.Layout
server.tsmain(), re-exportstools.tscallTool()dispatchssh-client.tsssh-config-parser.tsconfig-values.tshostMatchesAlias()platform.tstypes.tsbin/mcp-ssh.jsand the DXT package loaddist/server.js.dist/is generated and gitignored;npm installbuilds it viaprepare, and it ships inside the npm tarball.Strictness
strictplusnoUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride,noFallthroughCasesInSwitch,noPropertyAccessFromIndexSignatureandverbatimModuleSyntax. ESLint runstypescript-eslintstrictTypeChecked+stylisticTypeCheckedwith type information.Where a rule is relaxed the reason sits next to it. The one worth calling out:
prefer-nullish-coalescingexempts strings and numbers, because??is not interchangeable with||here — a stripped launcher environment reports an empty string for%ProgramData%(that is exactly what #10 describes), so??would silently reinstate that bug. Same forargs.timeout || DEFAULT, where 0 must fall back.Test files are checked under a lighter config (
tsconfig.test.json): mock objects and index-signature access are the normal vocabulary of a suite, and the tests are themselves the safety net.src/stays under the full strict set.Verification
bin/directly,npm start,start-silent.sh, an install from the packed tarball, and the DXT build all start the compiled server and answerinitialize/tools/listover STDIOtypecheck,lintand a smoke test of the compiled server, still across Linux and Windows on Node 20/22/24Cleanups that fell out
ts-nodeand@types/ssh2: the repo had no.tsfiles and never usedssh2test-helpers.tsuploadFile/downloadFilenow share one_scp()body — they differed only in argument order🤖 Generated with Claude Code